package com.niodata.dp.xls;

import com.google.common.base.Strings;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;

/**
 * parse csv , xls file schema.
 */
public class XlsSchemaInfer {

  public static XlsSchema inferSchema(File file,
        boolean isCsv,
        char delimiter, boolean firstLineAsHeader) throws IOException {
    return inferSchema(file, isCsv, delimiter, firstLineAsHeader, "UTF-8");
  }

  /**
   * parse csv schema from file.
   *
   * @param file csv file
   * @param isCsv is csv
   * @param delimiter delimiter
   * @param firstLineAsHeader first line as header
   * @param charSet charset
   * @return schema
   * @throws IOException ioe
   */
  public static XlsSchema inferSchema(File file,
        boolean isCsv,
        char delimiter, boolean firstLineAsHeader, String charSet) throws IOException {
    CSVFormat format = CSVFormat.DEFAULT.withDelimiter(delimiter);
    if (firstLineAsHeader) {
      format = CSVFormat.DEFAULT.withDelimiter(delimiter).withFirstRecordAsHeader()
            .withSkipHeaderRecord(true);
    }
    CSVParser parser = CSVParser.parse(file, Charset.forName(charSet), format);
    //int long double boolean date string
    List<CSVRecord> records = parser.getRecords();
    int maxLine = 2000;
    List<PossibleTypes> possibleTypes = buildPossibleTypes(records.get(0).size());
    int columns = records.get(0).size();
    List<String> headers = parser.getHeaderNames();
    if (!firstLineAsHeader) {
      headers = new ArrayList<>(columns);
      for (int i = 0; i < columns; i++) {
        headers.add("col_" + i);
      }
    }
    for (int i = 0; i < records.size() && i < maxLine; i++) {
      CSVRecord record = records.get(i);
      for (int j = 0; j < columns; j++) {
        possibleTypes.get(j).refinePossibleTypes(record.get(j));
      }
    }
    return buildTableSchema(possibleTypes, headers, records.get(0));
  }

  private static List<PossibleTypes> buildPossibleTypes(int size) {
    List<PossibleTypes> types = new ArrayList<>(size);
    for (int i = 0; i < size; i++) {
      types.add(PossibleTypes.newPossibleTypes());
    }
    return types;

  }

  private static XlsSchema buildTableSchema(List<PossibleTypes> types,
        List<String> headers, CSVRecord record) {
    List<XlsColumn> columns = new ArrayList<>();
    for (int i = 0; i < types.size(); i++) {
      XlsColumn col = new XlsColumn();
      columns.add(col);
      col.setName(headers.get(i));
      col.setDescription("");
      col.setExampleValue(record.get(i));
      col.setNullAble(true);
      col.setType(types.get(i).getType());
    }
    XlsSchema schema = new XlsSchema(columns);
    return schema;
  }

  private static class PossibleTypes {

    private boolean intType;
    private boolean longType;
    private boolean doubleType;
    private boolean booleanType;
    private boolean dateType;
    private boolean stringType;

    static PossibleTypes newPossibleTypes() {
      PossibleTypes types = new PossibleTypes();
      types.intType = true;
      types.booleanType = true;
      types.longType = true;
      types.doubleType = true;
      types.dateType = false;
      types.stringType = true;
      return types;
    }

    public void refinePossibleTypes(String rawData) {
      if (Strings.isNullOrEmpty(rawData)) {
        return;
      }
      if (intType) {
        try {
          Integer.parseInt(rawData);
        } catch (Exception e) {
          intType = false;
        }
      }

      if (longType) {
        try {
          Long.parseLong(rawData);
        } catch (Exception e) {
          longType = false;
        }
      }

      if (doubleType) {
        try {
          Double.parseDouble(rawData);
        } catch (Exception e) {
          doubleType = false;
        }
      }

      if (booleanType) {
        if (!"true".equals(rawData) && !"false".equals(rawData)) {
          booleanType = false;
        }
      }
    }

    public DataType getType() {
      if (intType) {
        return DataType.INT;
      }

      if (longType) {
        return DataType.LONG;
      }

      if (doubleType) {
        return DataType.DOUBLE;
      }

      if (booleanType) {
        return DataType.BOOLEAN;
      }

      return DataType.STRING;
    }
  }
}
